// SQLite implementation of simple database layer
// Copyright 2006 (C) Ralph Thomas

#include <util/db.h>
#include <toolkit/application.h>
#include <sqlite3.h>
#include <boost/filesystem/operations.hpp>
#include <boost/thread/recursive_mutex.hpp>
#include <map>

namespace bfs = boost::filesystem;

namespace util {
	struct dbImpl {
		typedef std::map<std::string, boost::weak_ptr<db> >		dbMap;
		typedef boost::recursive_mutex::scoped_lock	lock;
		typedef boost::recursive_mutex			mutex;
		//
		// If there are many different db instances over the lifetime
		// of the app then it's possible that this dbMap_s could become
		// large with dead weak pointers...
		//
		static dbMap dbMap_s;	///< Live db instances.
		
		dbImpl( sqlite3* data ) : database_m( data ) {}
		~dbImpl() {
			lock lk( mutex_m );
			if( !database_m ) return;
			sqlite3_close( database_m );
		}
		static void update( void* impl, int, const char* db_name, const char* table_name, sqlite_int64 ) {
			dbImpl* us( reinterpret_cast<dbImpl*>( impl ) );
			if( !us ) return;
			(us->on_change_m)();
		}
		static void signal_change( boost::weak_ptr<db> outer ) {
			db_ptr_t us( outer.lock() );
			if( !us ) return;
			us->_impl->on_change_m();
		}
		boost::weak_ptr<db>	us_m;
		sqlite3*		database_m;
		std::string		map_key_m;
		boost::signal<void()>	on_change_m;
		mutex			mutex_m;
	};
	dbImpl::dbMap dbImpl::dbMap_s;
	//
	/// Return a key into the static dbImpl::dbMap_s for the given
	/// database file path.
	///
	/// \param	p	the path to get the key for.
	/// \return	the key for the given path.
	//
	static std::string getMapKey( const bfs::path& p ) {
		return p.native_file_string();
	}
	
	static int process_row( void* results, int argc, char** argv, char** colName ) {
		db::on_row_proc_t* on_row_proc( reinterpret_cast<db::on_row_proc_t*>( results ) );
		if( !on_row_proc ) return 0;
		adobe::dictionary_t dict;
		for( int i = 0; i < argc; i++ ) {
			adobe::dictionary_t::value_type item;
			const char* column_cstr( colName[i] );
			const char* value_cstr( argv[i] );

			item.first = adobe::name_t( column_cstr );
			item.second = adobe::value_t( std::string( value_cstr ) );
			dict.insert( item );
		}
		if( *on_row_proc ) (*on_row_proc)( dict );
		return 0;
	}
	
	db::db( const boost::filesystem::path& path, bool create ) : _impl( 0 )
	{
		if( !create && !bfs::exists( path ) )
			throw std::runtime_error( path.native_file_string() + " does not exist" );
		sqlite3* database( 0 );
		int rc( sqlite3_open( path.native_file_string().c_str(), &database ) );
		if( rc != SQLITE_OK )
			throw std::runtime_error( path.native_file_string() + ": can't open" );
		_impl = new dbImpl( database );
		_impl->map_key_m = getMapKey( path );
	}
	db::~db() {
		if( _impl ) delete _impl;
	}

	db_ptr_t db::fromPath( const boost::filesystem::path& p ) {
		//
		// 1. Try to find this db in the dbImpl::dbMap_s.
		// 2. If it exists, retain and return it.
		// 3. If not, attempt to create a new db for it.
		// 4. Add it to the dbMap and return it.
		//
		db_ptr_t result;
		std::string key( getMapKey( p ) );
		dbImpl::dbMap::iterator i = dbImpl::dbMap_s.find( key );
		if( i != dbImpl::dbMap_s.end() ) {
			result = i->second.lock();
			if( result ) return result;
		}
		//
		// Create a new db. This will throw if it fails,
		// let the exception pass on up.
		//
		result = db_ptr_t( new db( p, true ) );
		//
		// Need a back-weak-pointer so that callbacks later
		// don't fire if the database has gone away. Also
		// need to abstract this pattern out as model would
		// benefit from it.
		//
		result->_impl->us_m = result;
		dbImpl::dbMap_s[key] = result;
		//
		// We already have a refcount of 1 just for being
		// created, don't increment it again.
		//
		return result;
	}
	long long int db::execute( const std::string& query ) {
		if( !_impl || !_impl->database_m )
			throw std::runtime_error( "no database open" );
		dbImpl::lock lk( _impl->mutex_m );
		char* error(0);
		int rc( sqlite3_exec( _impl->database_m, query.c_str(), 0, 0, &error ) );
		if( rc != SQLITE_OK ) {
			std::string msg;
			if( error ) {
				msg = error;
				sqlite3_free( error );
			}
			throw std::runtime_error( "SQLite: " + msg );
		}
		//
		// As we don't have sqlite3_update_hook available, just assume
		// that all executes cause a change to the db. We addref because
		// our caller may have released before the main-loop gets back
		// to signal_change.
		//
		toolkit::application::getInstance().executeInMain( boost::bind( &dbImpl::signal_change, _impl->us_m ) );
		return sqlite3_last_insert_rowid( _impl->database_m );
	}
	void db::query( const std::string& query, on_row_proc_t on_row ) {
		if( !_impl || !_impl->database_m )
			throw std::runtime_error( "no database open" );
		dbImpl::lock lk( _impl->mutex_m );
		char* error(0);
		int rc( sqlite3_exec( _impl->database_m, query.c_str(), &process_row, &on_row, &error ) );
		if( rc != SQLITE_OK ) {
			std::string msg;
			if( error ) {
				msg = error;
				sqlite3_free( error );
			}
			throw std::runtime_error( "SQLite: " + msg );
		}
	}
	boost::signals::connection db::on_change( const change_proc_t& proc ) {
		if( !_impl || !_impl->database_m )
			throw std::runtime_error( "no database open" );
		return _impl->on_change_m.connect( proc );
		//
		// XXX: sqlite3_update_hook is unavailable on the version of sqlite3
		// XXX: that ships with ubuntu :(.
		//
		//sqlite3_update_hook( _impl->database_m, &dbImpl::update, _impl );
	}
	std::string db::escape( const std::string& s ) {
		std::vector<std::string::value_type> escaped;
		escaped.reserve( s.size() );
		for( std::string::const_iterator i = s.begin(); i != s.end(); i++ ) {
			escaped.push_back( *i );
			if( *i == '\'' ) escaped.push_back( '\'' );
		}
		return std::string( &(*escaped.begin()), escaped.size() );
	}
};
